#include "../Include/WinApp.h"
#include <stdio.h>

#define USE_DSOUND 0

// Let this file know that somewhere out there, we have gpWinApp.
WinApp *gpWinApp = NULL;

#define WS_NORMAL_WINDOW (WS_OVERLAPPEDWINDOW & ~(WS_THICKFRAME|WS_MAXIMIZEBOX))
#define WS_FULLSCREEN_WINDOW (WS_POPUP)

WinApp::WinApp( const HINSTANCE hInst, const CHAR *pStr, const INT posX, const INT posY, const INT width, const INT height )
: m_hInstance( hInst ), m_appName( pStr ), m_appPosX( posX ), m_appPosY( posY ), m_appWidth( width ), m_appHeight( height ), 
  m_fullScreen( FALSE ), m_dt( 0.0f ), m_totalTime( 0.0f ), m_showFPS( FALSE ), m_paused( FALSE)
{
	PSX_ThrowAssert( width, "Invalid width." );
	PSX_ThrowAssert( height, "Invalid height." );

	PSX_MemSet( &m_VKMap, 0, sizeof( UCHAR ) * 256 );
	gpWinApp = this;
}

Pulse::BOOL WinApp::InitializeWindow( Pulse::BOOL showWindow /*= TRUE */, INT shwCmd /*= 1*/ )
{
	PSX_ZeroMem( &m_wndClass, sizeof( WNDCLASSEX ) );

	// This is a definition for the window we're creating.
	m_wndClass.cbSize			= sizeof( WNDCLASSEX );
	m_wndClass.style			= CS_HREDRAW | CS_VREDRAW /*| CS_DBLCLKS*/;
	m_wndClass.lpfnWndProc		= WinApp::WndProc;
	m_wndClass.cbClsExtra		= 0;
	m_wndClass.cbWndExtra		= 0;
	m_wndClass.hInstance		= m_hInstance;
	m_wndClass.hIcon			= LoadIcon( NULL, IDI_APPLICATION );
	m_wndClass.hCursor			= LoadCursor( NULL, IDC_ARROW );
	//m_wndClass.hbrBackground	= (HBRUSH)GetStockObject( GRAY_BRUSH );
	m_wndClass.lpszClassName	= "Game WinApp";
	m_wndClass.lpszMenuName		= NULL;
	m_wndClass.hIconSm			= NULL;

	// Register window class
	if ( !_RegisterWindowClass() ) return FALSE;

	//					Left,	Top,	Right,			Bottom
	//RECT clientRect = { m_appPosX, m_appPosY, m_appPosX + m_appWidth, m_appPosY + m_appHeight }; // Not used.

	// Create window. This won't show the window visually yet.
	if ( !_CreateWindow() ) return FALSE;

	// Create backbuffer
	m_pBackBuffer = new BackBuffer( m_hWnd, m_appWidth, m_appHeight );
	PSX_ThrowAssert( m_pBackBuffer, "Failed to allocate memory." );

	// Create sprite DC compatible for this dispaly
	try { PSX_ThrowAssert( m_spriteDC = CreateCompatibleDC( NULL ), "Failed to create Device Context." ); }
	catch ( Exception e )
	{
		MessageBox( NULL, e.GetMessage(), "Error", MB_ICONERROR );
		return FALSE;
	}

	// Initialize our sound manager

#if USE_DSOUND
	m_pDS  = CSGD_DirectSound::GetInstance();
	m_pWaveMan  = CSGD_WaveManager::GetInstance();

	if ( !m_pDS->InitDirectSound(m_hWnd) ) return false;
	if ( !m_pWaveMan->InitWaveManager(m_hWnd, m_pDS->GetDSObject()) ) return false;
#else
	m_pSoundSystem = SoundSystem::GetInstance();
	m_pSoundSystem->Initialize();
#endif

	if ( showWindow )
		ShowWindow( shwCmd );

	return TRUE;
}

Pulse::BOOL WinApp::SetWindowPos( INT newX, INT newY )
{
	if ( IsFullScreen() )
		return FALSE; // Set Pos denied if in fullscreen mode.

	m_appPosX = newX;
	m_appPosY = newY;

	::SetWindowPos( m_hWnd, 0, m_appPosX, m_appPosY, 0, 0,
		SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOZORDER );

	return TRUE;
}

Pulse::BOOL WinApp::SetWindowSize( INT width, INT height )
{
	if ( IsFullScreen() )
		return FALSE; // Do nothing if in fullscreen mode.

	PSX_ThrowAssert( width, "Width set to 0." );
	PSX_ThrowAssert( height, "Height set to 0." );

	m_appWidth = width;
	m_appHeight = height;

	if ( !ResizeWindow( 0, 0, width, height ) )
		return FALSE;

	return _OnDeviceReset();
}

Pulse::BOOL WinApp::ResizeWindow( INT newX, INT newY, INT width, INT height )
{
	if ( IsFullScreen() )
		return FALSE; // Set Pos denied if in fullscreen mode.

	if ( newX != 0 )
		m_appPosX = newX;
	if ( newY != 0 )
		m_appPosY = newY;
	if ( width != 0 )
		m_appWidth = width;
	if ( height != 0 )
		m_appHeight = height;

	RECT desiredRect;
	desiredRect.left	= m_appPosX;
	desiredRect.top		= m_appPosY;
	desiredRect.right	= m_appPosX + m_appWidth;
	desiredRect.bottom	= m_appPosY + m_appHeight;

	AdjustWindowRectEx( &desiredRect, GetWindowLongPtr( m_hWnd, GWL_STYLE ), 
		FALSE , GetWindowLongPtr( m_hWnd, GWL_EXSTYLE) );

	// We need to save then restore x and y position because WM_MOVE sets the position.
	INT appX(m_appPosX), appY(m_appPosY);
	::SetWindowPos( m_hWnd, HWND_NOTOPMOST, desiredRect.left, desiredRect.top, 
		desiredRect.right - desiredRect.left, desiredRect.bottom - desiredRect.top, SWP_SHOWWINDOW | SWP_NOZORDER );

	m_appPosX = appX;
	m_appPosY = appY;

	return _OnDeviceReset();
}

Pulse::BOOL WinApp::CenterWindow( void )
{
	if ( IsFullScreen() )
		return FALSE;	// Do nothing in fullscreen mode.

	m_appPosX		= GetSystemMetrics( SM_CXSCREEN )/2 - m_appWidth/2;
	m_appPosY		= GetSystemMetrics( SM_CYSCREEN )/2 - m_appHeight/2;

	return ResizeWindow( 0, 0, 0, 0 );
}

Pulse::BOOL WinApp::IsFullScreen( void )
{
	return m_fullScreen == TRUE;
}

void WinApp::ShowCursor( Pulse::BOOL bShow /*= TRUE*/ )
{
	::ShowCursor( bShow );
}

void WinApp::HideCursor( void )
{
	ShowCursor( FALSE );
}

Pulse::BOOL WinApp::IsBufferedKeyDown( INT vKey )
{
	PSX_Assert( vKey >= 0 && vKey <= 256, "vKey out of bound." );
	
	if ( m_VKMapGhost[vKey] )
		return TRUE;
	else
	{
		Pulse::BOOL keyDown = IsKeyDown( vKey );
		if ( m_VKMap[vKey] == 0 && keyDown == 1 )
			m_VKMapGhost[vKey] = m_VKMap[vKey] = 1;
		else if ( m_VKMap[vKey] == 1 && keyDown == 1 )
			return FALSE;
		else if ( m_VKMap[vKey] == 1 && keyDown == 0 )
			m_VKMap[vKey] = 0;
		else // Both are zero
			return FALSE;
	}

	// Case for the first and third if statements.
	return m_VKMap[vKey] ? TRUE : FALSE;
}

INT WinApp::GetAppCursorPos( POINT &point )
{
	if ( !::GetCursorPos( &point ) ) return FALSE;
	return ::ScreenToClient( m_hWnd, &point );
}

INT WinApp::GetCursorPos( POINT &point )
{
	return ::GetCursorPos( &point );
}

Pulse::BOOL WinApp::ToggleFullScreen( void )
{
	HDC hdc = GetDC( m_hWnd );
	INT refreshRate = GetDeviceCaps( hdc, VREFRESH );
	INT bitsPerPix = GetDeviceCaps( hdc, BITSPIXEL );
	ReleaseDC( m_hWnd, hdc );

	if ( IsFullScreen() ) // Restore back to non-fullscreen mode.
	{
		if ( !SetDisplayMode( 0, 0, 0, 0 ) )
			return FALSE; // Failed to switch back to non-fullscreen mode.

		// Set back to an overloapped window
		SetWindowLongPtr( m_hWnd, GWL_STYLE, WS_NORMAL_WINDOW );

		// Set menu back if we have a menu bar here...

		::SetWindowPos(m_hWnd, HWND_NOTOPMOST, 0, 0, 0, 0,
			SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE);

		RECT rCRect;
		rCRect.left = m_appPosX; rCRect.right = m_appPosX + m_appWidth;
		rCRect.top = m_appPosY; rCRect.bottom = m_appPosY + m_appHeight;
		
		// We don't have a meny so set it to FALSE
		/*AdjustWindowRectEx( &rCRect, GetWindowLongPtr( m_hWnd, GWL_STYLE), FALSE, 
			GetWindowLongPtr( m_hWnd, GWL_EXSTYLE ) );*/

		::SetWindowPos( m_hWnd, 0, rCRect.left, rCRect.top, 
			rCRect.right - rCRect.left, rCRect.bottom - rCRect.top, SWP_NOZORDER );

		// ShowCursur if we want to.
	}
	else // Go Full-Screen.
	{
		if ( !SetDisplayMode( m_appWidth, m_appHeight, bitsPerPix, refreshRate ) )
			return FALSE; // Failed to go to fullscreen mode.

		// In full screen, the window needs to be a popup window.
		SetWindowLongPtr( m_hWnd, GWL_STYLE, WS_FULLSCREEN_WINDOW );

		// This disables the menu.
		// SetMenu(m_hWnd, NULL);

		// Make the window topmost
		::SetWindowPos( m_hWnd, HWND_TOPMOST, 0, 0, 0, 0, 
			SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE );

		::SetWindowPos( m_hWnd, 0, 0, 0, 
			GetSystemMetrics( SM_CXSCREEN ), GetSystemMetrics( SM_CYSCREEN ), SWP_NOZORDER ); 
	}

	// Toggle bolean then finally call onReset
	m_fullScreen = !m_fullScreen;
	return _OnDeviceReset();
}

void WinApp::ShowWindow( INT shwCmd /*= 1*/ )
{
	::ShowWindow( m_hWnd, shwCmd );
	UpdateWindow( m_hWnd );

	// Resize then reposition
	ResizeWindow( 0, 0, 0, 0 );
	SetWindowPos( m_appPosX, m_appPosY );
	
}

INT WinApp::Run( void )
{
	// This is where all the magic starts.
	MSG message;
	PSX_ZeroMem( &message, sizeof( MSG ) );

	// Fire OnStart event
	if ( !OnStart() ) return -1;

	DWORD currTick;
	DWORD lastTick = GetTickCount();

	while ( message.message != WM_QUIT )
	{
		// Handle FPS debug info
		if ( m_showFPS ) 
			HandleFPS();

		if ( PeekMessage( &message, 0, 0, 0, PM_REMOVE ) )
		{
			// This code handles message events
			TranslateMessage( &message );
			DispatchMessage( &message );
			continue;
		}

		// Process game code if we don't have any windows messages.

		// Calculate delta time.
		// Note: There is a more accurate way to calculate delta-time. But this works fine for this sample.
		currTick = GetTickCount();
		m_dt = static_cast< float >(currTick - lastTick) / 1000.0f;
		m_dt = PSX_Max( m_dt, 0.0f );
		
		m_totalTime += m_dt;

		if ( m_paused == FALSE ) 
			if ( _OnUpdate() == FALSE )
				PostQuitMessage( 0 );

		OnRender();

		lastTick = currTick;
	}

	// Fires OnExit event internally
	if ( !_OnShutDown() ) return -1;

	return (INT)message.wParam;
}


/* Protected Methods */
Pulse::BOOL WinApp::SetDisplayMode( const INT width, const INT height, const INT bitsPerPix, const DWORD refreshRate )
{
	DEVMODE dm;

	dm.dmSize = sizeof( DEVMODE );

	// If all are zeroes, return to original mode
	if ( !width && !height && !bitsPerPix && !refreshRate )
	{
		ChangeDisplaySettings( NULL, 0 );
		return TRUE;
	}
	
	DWORD i = 0;

	// Enumerate available display settings
	while ( EnumDisplaySettings( NULL, i++, &dm ) )
	{
		// NOTE: Just look for greater than or equal to refreshRate for the refresh rate.
		if(dm.dmPelsWidth == width && dm.dmPelsHeight == height &&
			dm.dmBitsPerPel == bitsPerPix && dm.dmDisplayFrequency >= refreshRate)
		{
			if(ChangeDisplaySettings(&dm, CDS_TEST) == DISP_CHANGE_SUCCESSFUL)
			{
				ChangeDisplaySettings(&dm, CDS_FULLSCREEN);
				return TRUE;
			}
		}
	}

	return FALSE;
}

Pulse::BOOL WinApp::_RegisterWindowClass( void )
{
	try { PSX_ThrowAssert( ::RegisterClassEx( &m_wndClass ), "Failed to register window class."); }
	catch ( Exception e ) {	::MessageBox( NULL, e.GetMessage(), "Error", MB_ICONERROR ); return FALSE; }
	
	return TRUE;
}


Pulse::BOOL WinApp::_CreateWindow( void )
{
	m_hWnd = ::CreateWindowEx( 0, m_wndClass.lpszClassName, m_appName.GetCString(), WS_NORMAL_WINDOW, 
				m_appPosX, m_appPosY, m_appWidth, m_appHeight, NULL, NULL, m_hInstance, NULL );

	try { PSX_ThrowAssert( m_hWnd, "Failed to create window." ); }
	catch( Exception e ) { ::MessageBox( NULL, e.GetMessage(), "Error", MB_ICONERROR ); return FALSE; }
	
	return TRUE;
}

Pulse::BOOL WinApp::_OnDeviceReset( void )
{
	delete m_pBackBuffer;
	m_pBackBuffer = new BackBuffer( m_hWnd, m_appWidth, m_appHeight );
	try { PSX_ThrowAssert( m_pBackBuffer, "Failed to allocate memory." ); }
	catch ( Exception e )
	{
		MessageBox( NULL, e.GetMessage(), "Error", MB_ICONERROR );
		return FALSE;
	}

	// Recreate sprite DC for our new display
	DeleteDC( m_spriteDC );
	m_spriteDC = CreateCompatibleDC( NULL );

	try { PSX_ThrowAssert( m_spriteDC , "Failed to create device context." ); }
	catch ( Exception e )
	{
		MessageBox( NULL, e.GetMessage(), "Error", MB_ICONERROR );
		return FALSE;
	}

	return OnDeviceReset();
}

Pulse::BOOL WinApp::_OnUpdate( )
{
#if USE_DSOUND
	m_pWaveMan->Update();
#else
	m_pSoundSystem->Update();
#endif USE_DSOUND

	PSX_MemSetQuad( m_VKMapGhost, 0, 256/4 );

	// Constraint mouse
	POINT curAppPos;
	POINT curPos;
	GetAppCursorPos( curAppPos );
	GetCursorPos( curPos );

	if ( curAppPos.x < 0 )
		SetCursorPos( curPos.x - curAppPos.x, curPos.y );
	else if ( curAppPos.x > m_appWidth )
		SetCursorPos( curPos.x - (curAppPos.x - m_appWidth), curPos.y );	

	if ( curAppPos.y < 0 )
		SetCursorPos( curPos.x, curPos.y - curAppPos.y );
	else if ( curAppPos.y > m_appHeight )
		SetCursorPos( curPos.x, curPos.y - (curAppPos.y - m_appHeight) ) ;


	return OnUpdate( m_dt );
}

Pulse::BOOL WinApp::_OnShutDown( void )
{
	Pulse::BOOL ret = OnExit();

#if USE_DSOUND
	m_pWaveMan->ShutdownWaveManager();
	m_pDS->ShutdownDirectSound();
#else
	m_pSoundSystem->Shutdown();
	m_pSoundSystem->DeleteInstance();
	m_pSoundSystem = NULL;
#endif /* USE_DSOUND */

	return ret;
}


/* Event messages function handlers */
LRESULT WinApp::OnWMCreateWindow( void )
{
	// Default does nothing but return true
	return TRUE;
}

LRESULT WinApp::OnWMDestroyWindow( void )
{
	PostQuitMessage( 0 );
	return 0;
}

LRESULT WinApp::OnWMMove( void )
{
	RECT desiredRect = { LOWORD( lParam ), HIWORD( lParam ), LOWORD( lParam ) + m_appWidth, HIWORD( lParam ) + m_appHeight };

	AdjustWindowRectEx( &desiredRect, GetWindowLongPtr( m_hWnd, GWL_STYLE ), FALSE, GetWindowLongPtr( m_hWnd, GWL_EXSTYLE ) );
	
	m_appPosX = desiredRect.left;
	m_appPosY = desiredRect.top;

	return 0;
}

LRESULT WinApp::OnWMPaint( void )
{
	return DefWindowProc( m_hWnd, msg, wParam, lParam );
}

LRESULT WinApp::OnWMGainFocus()
{
	m_paused = FALSE;
	return 0;
}

LRESULT WinApp::OnWMLoseFocus()
{
	m_paused = TRUE;
	return 0;
}

LRESULT WinApp::OnWMQuit( void )
{
	return DefWindowProc( m_hWnd, msg, wParam, lParam );
}


/* Windows Procedures */
LRESULT CALLBACK WinApp::WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
	gpWinApp->msg = msg;
	gpWinApp->wParam = wParam;
	gpWinApp->lParam = lParam;

	// Messages will be delegated to other functions "On...()" as if it fired an event.
	switch( msg )
	{
	case WM_CREATE:
		return gpWinApp->OnWMCreateWindow();
	case WM_PAINT:
		return gpWinApp->OnWMPaint();
	case WM_ACTIVATE:
		if ( LOWORD( wParam ) == WA_INACTIVE )
			return gpWinApp->OnWMLoseFocus();
		else
			return gpWinApp->OnWMGainFocus();
	case WM_MOVE:
		return gpWinApp->OnWMMove();
	case WM_QUIT:
		return gpWinApp->OnWMQuit();
	//case WM_KILLFOCUS:
	case WM_DESTROY:
		return gpWinApp->OnWMDestroyWindow();
	}

	// Pass it to default procedure if unhandled.
	return DefWindowProc( hWnd, msg, wParam, lParam );
}

/* Private Methods */
//void WinApp::HandleFPS( void )
//{
//	for( UINT i = 0; i < 256; ++i )
//		m_VKMap = IsKeyDown( i );
//}

void WinApp::HandleFPS( void )
{
	static UINT		totalFrames = 0;
	static FLOAT	elapsedTime = 0.0f;

	elapsedTime += GetElapsedTime();
	++totalFrames;

	if ( elapsedTime >= 1.0f )
	{
		CHAR str[255];

		sprintf_s( str, 255,"FPS: %d", totalFrames );
		SetWindowText( m_hWnd, str );

		elapsedTime = 0.0f;
		totalFrames = 0;
	}
}

bool RectRectCollisionCheck( RECT &r1, RECT &r2 )
{
	if ( r1.right < r2.left || r2.right < r1.left || 
		r1.bottom < r2.top || r2.bottom < r1.top )
		return false;

	return true;
}
